#include "vcl_stl.h"

#include "bkbase.h"
#include "bstcp.h"

#include "h_base.h"
#include "h_xml.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

#include <fnmatch.h>

static char *SimbatBase = NULL;
static HString JobId;
static map<HString, HString> ArgMap;
static vector<HIniSection> SectionVec;
static HString TagRetCode;
static HString TagErrMsg;
static HString TagInquireResult;
static HString TagInquireMsg;
static HString FuncJobStart;
static HString FuncJobInquire;
static HString FuncJobCancel;
static HString RetCodeSuccess;
static HString ResultSuccess;
static HString ResultBreak;
static HString ResultRunning;
static HString Url;
static HString BodyStart;
static HString BodyInquire;
static HString BodyCancel;
static HString HeaderStart;
static HString HeaderInquire;
static HString HeaderCancel;
static vector<HString> SetvarNameVec;
static vector<HString> SetvarTagVec;
static HString TagPartitionFile;
static HString Addr;
static int Port = 80;
static HString FuncRoot;
static bool ExitFlag = false;

#define FORMAT_JSON 0
#define FORMAT_XML  1

static int Format = FORMAT_JSON;

#define TIME_OUT 10000

static void SignalHandler(int)
{
   ExitFlag = true;
}

static HString TranslateNewline(const HString &Str, const HString &Repl)
{
   HString Result;

   for ( const char *s = Str.c_str(); *s != '\0'; )
   {
      const char *Start = s;
      for ( ; *s != '\0' && *s != '\n'; s++ )
         ;
      Result += HString(Start, s - Start);
      if ( *s == '\n' )
      {
         Result += Repl;
         s++;
      }
   }

   return Result;
}

static void JsonGetTag(const char *Begin, const HString &Tag, HString &Value)
{
   const char *s;
   char TagStr[500 + 1];
   bool XmlPropertyFlag = false;

   Value = "";

   if ( Format == FORMAT_JSON )
   {
      snprintf(TagStr, 500, "\"%s\":", Tag.c_str());
      s = strstr(Begin, TagStr);
      if ( s == NULL )
      {
         return;
      }
   }
   else
   {
      snprintf(TagStr, 500, "<%s>", Tag.c_str());
      s = strstr(Begin, TagStr);
      if ( s == NULL )
      {
         snprintf(TagStr, 500, " %s=", Tag.c_str());
         s = strstr(Begin, TagStr);
         if ( s == NULL )
         {
            return;
         }
         XmlPropertyFlag = true;
      }
   }

   for ( s += strlen(TagStr); *s == ' '; s++ )
      ;

   if ( ( Format == FORMAT_JSON || XmlPropertyFlag ) && *s == '"' )
   {
      s++;
      const char *Word = s;
      for ( ; *s != '\0' && *s != '"'; s++ )
         ;
      Value = HString(Word, s - Word);
      return;
   }

   const char *End;

   if ( Format == FORMAT_JSON )
   {
      End = ",}\"";
   }
   else
   {
      if ( XmlPropertyFlag )
      {
         End = " >";
      }
      else
      {
         End = "<";
      }
   }

   {
      const char *Word = s;
      for ( ; *s != '\0' && !CharInStr(*s, End); s++ )
         ;
      Value = HString(Word, s - Word);
   }
}

static bool HttpPost(const char *Header, const HString &Body, char OutputBuf[4096 + 1],
   HString &RetCode, HString &ErrMsg, HString &InquireResult, HString &InquireMsg,
   bool &TimeoutFlag, bool SetvarFlag)
{
   RetCode = "";
   ErrMsg = "";
   InquireResult = "";
   InquireMsg = "";

   int Sock = BsCreateClientSocket(Addr.c_str(), Port, TIME_OUT);

   if ( Sock < 0 )
   {
      BkPrintError("BsCreateClientSocket(%s, %d) fail, errno = %d", Addr.c_str(), Port, errno);
      return false;
   }

   int r = BsWriteToSocket(Sock, Header, strlen(Header), TIME_OUT);

   if ( r < 0 )
   {
      BkPrintError("BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      close(Sock);
      return false;
   }

   r = BsWriteToSocket(Sock, Body.c_str(), Body.length(), TIME_OUT);

   if ( r < 0 )
   {
      BkPrintError("BsWriteToSocket fail, r = %d, errno = %d", r, errno);
      close(Sock);
      return false;
   }

   int Len = 0;

   for ( ; Len < 4096; )
   {
      r = BsReadFromSocketMaxLen(Sock, OutputBuf + Len, 4096 - Len, TimeoutFlag ? 3000 : TIME_OUT);
      if ( r == 0 )
      {
         break;
      }
      if ( TimeoutFlag && r == BS_ETIMEOUT )
      {
         close(Sock);
         return true;
      }
      if ( r < 0 )
      {
         BkPrintError("BsReadFromSocket fail, r = %d, errno = %d", r, errno);
         close(Sock);
         return false;
      }
      Len += r;
   }

   OutputBuf[Len] = '\0';
   close(Sock);

   {
      RetCode = "";
      ErrMsg = "";
      InquireResult = "";
      InquireMsg = "";
      const char *s = strstr(OutputBuf, "\r\n\r\n");
      if ( s == NULL )
      {
         return true;
      }
      const char *Begin = s + 4;
      JsonGetTag(Begin, TagRetCode, RetCode);
      JsonGetTag(Begin, TagErrMsg, ErrMsg);
      JsonGetTag(Begin, TagInquireResult, InquireResult);
      JsonGetTag(Begin, TagInquireMsg, InquireMsg);
      if ( SetvarFlag )
      {
         for ( int i = 0; i < SetvarTagVec.size(); i++ )
         {
            HString &Tag = SetvarTagVec[i];
            HString &Name = SetvarNameVec[i];
            HString Value;
            JsonGetTag(Begin, Tag, Value);
            if ( Value != "" )
            {
               char Command[500 + 1];
               snprintf(Command, 500, "simbat_setvar %s '%s'", Name.c_str(), Value.c_str());
               BkPrintError("### execute %s", Command);
               system(Command);
            }
         }
         if ( TagPartitionFile != "" )
         {
            HString Value;
            JsonGetTag(Begin, TagPartitionFile, Value);
            vector<HString> StrVec;
            SplitString(Value.c_str(), ',', StrVec, true);
            for ( int i = 0; i < StrVec.size(); i++ )
            {
               char Command[500 + 1];
               snprintf(Command, 500, "simbat_backup_file part/%s ", StrVec[i].c_str());
               BkPrintError("### execute %s", Command);
               system(Command);
            }
         }
      }
   }

   TimeoutFlag = false;
   return true;
}

static void PrintUsage()
{
   fprintf(stderr, "Usage : [PATH/]simbat_http_post [--config=config_file] job_id ...\n");
}

int main(int argc, char *argv[])
{
   if ( argc == 1 )
   {
      PrintUsage();
      return 8;
   }

   if ( strchr(argv[0], '/') != NULL )
   {
      SetBaseByPath(argv[0]);
   }

   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      BkPrintError("$SIMBAT_BASE not defined");
      return 8;
   }

   BkSignal(SIGINT, SignalHandler, 0);

   int i = 1;
   char FileName[500 + 1];
   char ErrMsg[8192 + 1];

   snprintf(FileName, 500, "%s/etc/http_post.ini", SimbatBase);

   if ( !LoadIniFile(FileName, SectionVec, ErrMsg, true) )
   {
      BkPrintError("Load ini file '%s' fail, %s", FileName, ErrMsg);
      return 8;
   }

   if ( i < argc && memcmp(argv[i], "--config=", 9) == 0 )
   {
      BkStrNCpy(FileName, argv[i] + 9, sizeof(FileName) - 1);
      i++;
   }

   int Start = i;

   JobId = argv[i];

   for ( ; i < argc; i++ )
   {
      char TmpStr[200 + 1];
      snprintf(TmpStr, 200, "%d", i - Start + 1);
      ArgMap[TmpStr] = argv[i];
   }

   BkPrintError("LoadIniFile(%s)", FileName);

   if ( !LoadIniFile(FileName, SectionVec, ErrMsg, false) )
   {
      BkPrintError("Load ini file '%s' fail, %s", FileName, ErrMsg);
      return 8;
   }

   TagRetCode = "ret_code";
   TagErrMsg = "err_msg";
   TagInquireResult = "inquire_result";
   TagInquireMsg = "inquire_msg";
   FuncJobStart = "job_start";
   FuncJobCancel = "";
   FuncJobInquire = "job_inquire";
   RetCodeSuccess = "0";
   ResultSuccess = "0";
   ResultBreak = "1";
   ResultRunning = "2";
   Url = "127.0.0.1:8080";
   BodyStart = EnvEvaluate("{\"job_id\":\"${1}\",\"job_seq\":\"${SIMBAT_JOB_SEQ}\"}");
   BodyInquire = BodyStart;
   BodyCancel = BodyStart;
   HeaderStart = "";
   HeaderInquire = HeaderStart;
   HeaderCancel = HeaderStart;

#define SET_FROM_ITEM_MAP(ItemName, Var) \
      if ( Section.ItemMap.find(ItemName) != Section.ItemMap.end() ) \
      { \
         Var = EnvEvaluate(Section.ItemMap[ItemName], &ArgMap); \
      }

   for ( int i = 0; i < SectionVec.size(); i++ )
   {
      HIniSection &Section = SectionVec[i];
      if ( Section.Name != "common" )
      {
         continue;
      }
      SET_FROM_ITEM_MAP("tag_ret_code", TagRetCode)
      SET_FROM_ITEM_MAP("tag_err_msg", TagErrMsg)
      SET_FROM_ITEM_MAP("tag_inquire_result", TagInquireResult)
      SET_FROM_ITEM_MAP("tag_inquire_msg", TagInquireMsg)
      SET_FROM_ITEM_MAP("func_job_start", FuncJobStart)
      SET_FROM_ITEM_MAP("func_job_inquire", FuncJobInquire)
      SET_FROM_ITEM_MAP("func_job_cancel", FuncJobCancel)
      SET_FROM_ITEM_MAP("ret_code_success", RetCodeSuccess)
      SET_FROM_ITEM_MAP("result_success", ResultSuccess)
      SET_FROM_ITEM_MAP("result_break", ResultBreak)
      SET_FROM_ITEM_MAP("result_running", ResultRunning)
      SET_FROM_ITEM_MAP("url", Url)
      SET_FROM_ITEM_MAP("body_start", BodyStart)
      SET_FROM_ITEM_MAP("body_inquire", BodyInquire)
      SET_FROM_ITEM_MAP("body_cancel", BodyCancel)
      SET_FROM_ITEM_MAP("header_start", HeaderStart)
      SET_FROM_ITEM_MAP("header_inquire", HeaderInquire)
      SET_FROM_ITEM_MAP("header_cancel", HeaderCancel)
      HString FormatStr;
      SET_FROM_ITEM_MAP("format", FormatStr)
      ToUpper(FormatStr);
      if ( FormatStr == "XML" )
      {
         Format = FORMAT_XML;
      }
      else if ( FormatStr == "JSON" )
      {
         Format = FORMAT_JSON;
      }
      HString SetvarNameStr;
      SET_FROM_ITEM_MAP("setvar_name", SetvarNameStr)
      if ( SetvarNameStr != "" )
      {
         SplitString(SetvarNameStr.c_str(), ',', SetvarNameVec, true);
         HString SetvarTagStr;
         SET_FROM_ITEM_MAP("setvar_tag", SetvarTagStr)
         SplitString(SetvarTagStr.c_str(), ',', SetvarTagVec, true);
         if ( SetvarNameVec.size() != SetvarTagVec.size() )
         {
            BkPrintError("http_post.ini, setvar_name & setvar_tag not match");
            return 8;
         }
      }
      SET_FROM_ITEM_MAP("tag_partition_file", TagPartitionFile)
   }

   bool MatchFlag = false;

   for ( int i = 0; i < SectionVec.size(); i++ )
   {
      HIniSection &Section = SectionVec[i];
      if ( Section.Name == "common" )
      {
         continue;
      }
      vector<HString> StrVec;
      SplitString(Section.Name.c_str(), ',', StrVec, true);
      for ( int j = 0; j < StrVec.size(); j++ )
      {
         if ( fnmatch(StrVec[j].c_str(), JobId.c_str(), 0) == 0 )
         {
            MatchFlag = true;
            break;
         }
      }
      if ( !MatchFlag )
      {
         continue;
      }
      SET_FROM_ITEM_MAP("tag_ret_code", TagRetCode)
      SET_FROM_ITEM_MAP("tag_err_msg", TagErrMsg)
      SET_FROM_ITEM_MAP("tag_inquire_result", TagInquireResult)
      SET_FROM_ITEM_MAP("tag_inquire_msg", TagInquireMsg)
      SET_FROM_ITEM_MAP("func_job_start", FuncJobStart)
      SET_FROM_ITEM_MAP("func_job_inquire", FuncJobInquire)
      SET_FROM_ITEM_MAP("func_job_cancel", FuncJobCancel)
      SET_FROM_ITEM_MAP("ret_code_success", RetCodeSuccess)
      SET_FROM_ITEM_MAP("result_success", ResultSuccess)
      SET_FROM_ITEM_MAP("result_break", ResultBreak)
      SET_FROM_ITEM_MAP("result_running", ResultRunning)
      SET_FROM_ITEM_MAP("url", Url)
      SET_FROM_ITEM_MAP("body_start", BodyStart)
      SET_FROM_ITEM_MAP("body_inquire", BodyInquire)
      SET_FROM_ITEM_MAP("body_cancel", BodyCancel)
      SET_FROM_ITEM_MAP("header_start", HeaderStart)
      SET_FROM_ITEM_MAP("header_inquire", HeaderInquire)
      SET_FROM_ITEM_MAP("header_cancel", HeaderCancel)
      HString FormatStr;
      SET_FROM_ITEM_MAP("format", FormatStr)
      ToUpper(FormatStr);
      if ( FormatStr == "XML" )
      {
         Format = FORMAT_XML;
      }
      else if ( FormatStr == "JSON" )
      {
         Format = FORMAT_JSON;
      }
      HString SetvarNameStr;
      SET_FROM_ITEM_MAP("setvar_name", SetvarNameStr)
      if ( SetvarNameStr != "" )
      {
         SplitString(SetvarNameStr.c_str(), ',', SetvarNameVec, true);
         HString SetvarTagStr;
         SET_FROM_ITEM_MAP("setvar_tag", SetvarTagStr)
         SplitString(SetvarTagStr.c_str(), ',', SetvarTagVec, true);
         if ( SetvarNameVec.size() != SetvarTagVec.size() )
         {
            BkPrintError("http_post.ini, setvar_name & setvar_tag not match");
            return 8;
         }
      }
      SET_FROM_ITEM_MAP("tag_partition_file", TagPartitionFile)
      break;
   }

   BodyStart = TranslateNewline(BodyStart, " ");
   BodyInquire = TranslateNewline(BodyInquire, " ");
   BodyCancel = TranslateNewline(BodyCancel, " ");
   HeaderStart = TranslateNewline(HeaderStart, "\r\n");
   HeaderInquire = TranslateNewline(HeaderInquire, "\r\n");
   HeaderCancel = TranslateNewline(HeaderCancel, "\r\n");

   if ( !MatchFlag )
   {
      BkPrintError("JobId %s not defined in http_post.ini", JobId.c_str());
      return 8;
   }

   printf("\n");
   printf("TagRetCode = %s\n", TagRetCode.c_str());
   printf("TagErrMsg = %s\n", TagErrMsg.c_str());
   printf("TagInquireResult = %s\n", TagInquireResult.c_str());
   printf("TagInquireMsg = %s\n", TagInquireMsg.c_str());
   printf("FuncJobStart = %s\n", FuncJobStart.c_str());
   printf("FuncJobInquire = %s\n", FuncJobInquire.c_str());
   printf("FuncJobCancel = %s\n", FuncJobCancel.c_str());
   printf("RetCodeSuccess = %s\n", RetCodeSuccess.c_str());
   printf("ResultSuccess = %s\n", ResultSuccess.c_str());
   printf("ResultBreak = %s\n", ResultBreak.c_str());
   printf("ResultRunning = %s\n", ResultRunning.c_str());
   printf("Url = %s\n", Url.c_str());
   printf("BodyStart = %s\n", BodyStart.c_str());
   printf("\n");

   {
      const char *s = Url.c_str();
      int Pos = 0;
      if ( memcmp(s, "http://", 7) == 0 )
      {
         Pos += 7;
      }
      int Begin = Pos;
      for ( ; s[Pos] != '\0' && s[Pos] != ':' && s[Pos] != '/'; Pos++ )
         ;
      Addr = HString(s + Begin, Pos - Begin);
      if ( s[Pos] == ':' )
      {
         Pos++;
         Port = StrToInt(s + Pos);
         for ( ; s[Pos] != '\0' && s[Pos] != '/'; Pos++ )
            ;
      }
      FuncRoot = HString(s + Pos);
      if ( BkStrRCmp(FuncRoot.c_str(), "/") != 0 )
      {
         FuncRoot = FuncRoot + "/";
      }
      printf("Addr = %s\n", Addr.c_str());
      printf("Port = %d\n", Port);
      printf("FuncRoot = %s\n", FuncRoot.c_str());
      printf("\n");
   }

   char Header[2048 + 1];
   char Buf[4096 + 1];

   snprintf(Header, 2048,
      "POST %s%s HTTP/1.1\r\n"
      "Host: %s:%d\r\n"
      "Content-Type: application/json\r\n"
      "Content-Length: %d\r\n"
      "Connection: close\r\n"
      "%s%s"
      "\r\n",
      FuncRoot.c_str(), FuncJobStart.c_str(), Addr.c_str(), Port, BodyStart.length(),
      HeaderStart == "" ? "" : HeaderStart.c_str(),
      HeaderStart == "" ? "" : "\r\n");

   BkPrintError("HTTP POST, func = %s, Request : ", FuncJobStart.c_str());
   printf("%s%s\n", Header, BodyStart.c_str());
   fflush(stdout);
   BkMemDump(stdout, BodyStart.c_str(), BodyStart.length());
   fflush(stdout);

   HString RetCode;
   HString ErrMsgStr;
   HString InquireResult;
   HString InquireMsg;
   bool TimeoutFlag = true;

   if ( !HttpPost(Header, BodyStart, Buf, RetCode, ErrMsgStr, InquireResult, InquireMsg, TimeoutFlag, true) )
   {
      return 8;
   }

   if ( TimeoutFlag )
   {
      printf("No response in %d seconds\n", 3);
      fflush(stdout);
   }
   else
   {
      printf("Response :\n%s\n", Buf);
      fflush(stdout);
      BkMemDump(stdout, Buf, strlen(Buf));
      if ( RetCode != RetCodeSuccess )
      {
         BkPrintError("Start job fail, RetCode = %s, ErrMsg = %s", RetCode.c_str(), ErrMsgStr.c_str());
         return 8;
      }
   }

   if ( InquireResult == ResultSuccess )
   {
      return 0;
   }

   if ( InquireResult == ResultBreak )
   {
      return 59;
   }

   if ( InquireResult != "" && InquireResult != ResultRunning )
   {
      BkPrintError("InquireResult = %s, InquireMsg = %s", InquireResult.c_str(), InquireMsg.c_str());
      return 8;
   }

   int ErrorQty = 0;
   int InquireQty = 0;

   TimeoutFlag = false;

   for ( ; ; )
   {
      sleep(3);
      if ( ExitFlag )
      {
         if ( FuncJobCancel != "" )
         {
            snprintf(Header, 2048,
               "POST %s%s HTTP/1.1\r\n"
               "Host: %s:%d\r\n"
               "Content-Type: application/json\r\n"
               "Content-Length: %d\r\n"
               "Connection: close\r\n"
               "\r\n",
               FuncRoot.c_str(), FuncJobCancel.c_str(), Addr.c_str(), Port, BodyCancel.length());
            HttpPost(Header, BodyCancel.c_str(), Buf, RetCode, ErrMsgStr, InquireResult, InquireMsg, TimeoutFlag, false);
         }
         BkPrintError("User cancel");
         return 8;
      }
      snprintf(Header, 2048,
         "POST %s%s HTTP/1.1\r\n"
         "Host: %s:%d\r\n"
         "Content-Type: application/json\r\n"
         "Content-Length: %d\r\n"
         "Connection: close\r\n"
         "%s%s"
         "\r\n",
         FuncRoot.c_str(), FuncJobInquire.c_str(), Addr.c_str(), Port, BodyInquire.length(),
         HeaderInquire == "" ? "" : HeaderInquire.c_str(),
         HeaderInquire == "" ? "" : "\r\n");
      if ( !HttpPost(Header, BodyInquire.c_str(), Buf, RetCode, ErrMsgStr, InquireResult, InquireMsg, TimeoutFlag, true) )
      {
         if ( ExitFlag )
         {
            if ( FuncJobCancel != "" )
            {
               snprintf(Header, 2048,
                  "POST %s%s HTTP/1.1\r\n"
                  "Host: %s:%d\r\n"
                  "Content-Type: application/json\r\n"
                  "Content-Length: %d\r\n"
                  "Connection: close\r\n"
                  "%s%s"
                  "\r\n",
                  FuncRoot.c_str(), FuncJobCancel.c_str(), Addr.c_str(), Port, BodyCancel.length(),
                  HeaderCancel == "" ? "" : HeaderCancel.c_str(),
                  HeaderCancel == "" ? "" : "\r\n");
               HttpPost(Header, BodyCancel.c_str(), Buf, RetCode, ErrMsgStr, InquireResult, InquireMsg, TimeoutFlag, false);
            }
            BkPrintError("User cancel");
            return 8;
         }
         ErrorQty++;
         if ( ErrorQty <= 60 )
         {
            continue;
         }
         return 8;
      }
      if ( InquireResult == ResultRunning )
      {
         InquireQty++;
         if ( InquireQty % 20 == 0 )
         {
            BkPrintError("InquireQty = %d", InquireQty);
         }
         continue;
      }
      BkPrintError("HTTP POST, func = %s, Request : ", FuncJobInquire.c_str());
      printf("%s%s\n", Header, BodyInquire.c_str());
      BkMemDump(stdout, BodyInquire.c_str(), BodyInquire.length());
      fflush(stdout);
      printf("Response :\n%s\n", Buf);
      fflush(stdout);
      BkMemDump(stdout, Buf, strlen(Buf));
      fflush(stdout);
      if ( InquireResult == ResultSuccess )
      {
         break;
      }
      if ( InquireResult == ResultBreak )
      {
         return 59;
      }
      BkPrintError("InquireResult = %s, InquireMsg = %s", InquireResult.c_str(), InquireMsg.c_str());
      if ( InquireResult == "" )
      {
         ErrorQty++;
         if ( ErrorQty <= 60 )
         {
            continue;
         }
      }
      return 8;
   }

   return 0;
}
